package com.automata.components 
{
	import com.automata.data.HangulInfo;
	import com.automata.data.WriteInfo;
	import com.automata.status.HangulCode;
	import com.automata.utils.HangulUtil;
	
	import flash.events.EventDispatcher;

	/**
	 * ...
	 * @author kssuman
	 */
	public class HangulAutomata
	{
		private const CHO:int = 1;		// bit : 001
		private const JUNG:int = 2;		// bit : 010
		private const JONG:int = 4;		// bit : 100
		
		private var keyTable:Object;
		private var prevCode:uint;
		//private var currCode:uint;
		
		public function HangulAutomata() 
		{
			HangulUtil.init();
			
			keyTable = 
			{
				q:HangulCode.CHOSEONG_PIEUP,
				w:HangulCode.CHOSEONG_CIEUC,
				e:HangulCode.CHOSEONG_TIKEUT,
				r:HangulCode.CHOSEONG_KIYEOK,
				t:HangulCode.CHOSEONG_SIOS,
				a:HangulCode.CHOSEONG_MIEUM,
				s:HangulCode.CHOSEONG_NIEUN,
				d:HangulCode.CHOSEONG_IEUNG,
				f:HangulCode.CHOSEONG_RIEUL,
				g:HangulCode.CHOSEONG_HIEUH,
				z:HangulCode.CHOSEONG_KHIEUKH,
				x:HangulCode.CHOSEONG_THIEUTH,
				c:HangulCode.CHOSEONG_CHIEUCH,
				v:HangulCode.CHOSEONG_PHIEUPH,
				Q:HangulCode.CHOSEONG_SSANGPIEUP,
				W:HangulCode.CHOSEONG_SSANGCIEUC,
				E:HangulCode.CHOSEONG_SSANGTIKEUT,
				R:HangulCode.CHOSEONG_SSANGKIYEOK,
				T:HangulCode.CHOSEONG_SSANGSIOS,
				A:HangulCode.CHOSEONG_MIEUM,
				S:HangulCode.CHOSEONG_NIEUN,
				D:HangulCode.CHOSEONG_IEUNG,
				F:HangulCode.CHOSEONG_RIEUL,
				G:HangulCode.CHOSEONG_HIEUH,
				Z:HangulCode.CHOSEONG_KHIEUKH,
				X:HangulCode.CHOSEONG_THIEUTH,
				C:HangulCode.CHOSEONG_CHIEUCH,
				V:HangulCode.CHOSEONG_PHIEUPH,
				y:HangulCode.JUNGSEONG_YO,
				u:HangulCode.JUNGSEONG_YEO,
				i:HangulCode.JUNGSEONG_YA,
				o:HangulCode.JUNGSEONG_AE,
				p:HangulCode.JUNGSEONG_E,
				h:HangulCode.JUNGSEONG_O,
				j:HangulCode.JUNGSEONG_EO,
				k:HangulCode.JUNGSEONG_A,
				l:HangulCode.JUNGSEONG_I,
				b:HangulCode.JUNGSEONG_YU,
				n:HangulCode.JUNGSEONG_U,
				m:HangulCode.JUNGSEONG_EU,
				Y:HangulCode.JUNGSEONG_YO,
				U:HangulCode.JUNGSEONG_YEO,
				I:HangulCode.JUNGSEONG_YA,
				O:HangulCode.JUNGSEONG_YAE,
				P:HangulCode.JUNGSEONG_YE,
				H:HangulCode.JUNGSEONG_O,
				J:HangulCode.JUNGSEONG_EO,
				K:HangulCode.JUNGSEONG_A,
				L:HangulCode.JUNGSEONG_I,
				B:HangulCode.JUNGSEONG_YU,
				N:HangulCode.JUNGSEONG_U,
				M:HangulCode.JUNGSEONG_EU
			};
		}
		
		/**
		 * 한글 오토마타를 초기화 하는 함수
		 * initializeCode 함수 실행후, 한글을 새로 조합 할 준비를 함
		 */		
		public function initializeCode():void {
			prevCode = 0;
		}
		
		/**
		 * 키보드의 한글 영타를 넣어서 한글을 조합하는 함수
		 *  
		 * @param key 키보드의 한글 영타
		 * @return 한글이 조합된 정보를 리턴
		 * 
		 * @example TextInput에 '깍하'라는 글자를 넣는 예제
		 * 
		 * <listing>
		 * <?xml version="1.0" encoding="utf-8"?>
		 * <s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
		 *						   xmlns:s="library://ns.adobe.com/flex/spark" 
		 *						   xmlns:mx="library://ns.adobe.com/flex/mx"
		 *						   contentCreationComplete="create()"
		 *						   >
		 *		<fx:Declarations>
		 *			<!-- Place non-visual elements (e.g., services, value objects) here -->
		 *		</fx:Declarations>
		 *		<fx:Script>
		 *			<![CDATA[
		 *				import com.automata.components.HangulAutomata;
		 *				import com.automata.data.HangulInfo;
		 *				import com.automata.data.WriteInfo;
		 *				import com.automata.status.HangulCode;
		 *				
		 *				import mx.core.IUITextField;
		 *				import mx.core.mx_internal;
		 *				
		 *				private var automata:HangulAutomata;
		 *				private var textField:IUITextField;
		 *				private var currIndex:int = -1;
		 *				
		 *				private function create():void {
		 *					textField = output.mx_internal::getTextField();
		 *					
		 *					automata = new HangulAutomata();
		 *					updateOutput( automata.addHanKey( "R" ) );	// ㄲ
		 *					updateOutput( automata.addHanKey( "k" ) );	// 까
		 *					updateOutput( automata.addHanKey( "r" ) );	// [깍]
		 *					updateOutput( automata.addHanKey( "g" ) );	// ㅎ
		 *					updateOutput( automata.addHanKey( "k" ) );	// [하]
		 *				}
		 *				
		 *				private function updateOutput(info:WriteInfo):void {
		 *					var newHangul:String;
		 *					if (info.prevHangulInfo) {
		 *						textField.replaceText(currIndex, (currIndex + 1), "");
		 *						newHangul = info.prevHangulInfo.hangul;
		 *					}
		 *					if (info.currHangulInfo) {
		 *						newHangul = info.currHangulInfo.hangul;
		 *						currIndex++;
		 *					}
		 *					textField.appendText( newHangul );
		 *				}
		 *			]]>
		 * 		</fx:Script>
		 *		<s:Group width="100%" height="100%">
		 *			<mx:TextInput id="output" horizontalCenter="0" verticalCenter="0" />
		 *		</s:Group>
		 *	</s:WindowedApplication>
		 * </listing>
		 * 
		 */		
		public function addHanKey(key:String):WriteInfo
		{
			return addHan( keyTable[key] );
		}
		
		/**
		 * 한글 코드를 넣어서 한글을 조합하는 함수
		 *  
		 * @param hangulCode HangulCode의 한글 자모 상수값
		 * @return 한글이 조합된 정보를 리턴
		 * 
		 * @example TextInput에 '각하'라는 글자를 넣는 예제
		 * 
		 * <listing>
		 * <?xml version="1.0" encoding="utf-8"?>
		 * <s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
		 *						   xmlns:s="library://ns.adobe.com/flex/spark" 
		 *						   xmlns:mx="library://ns.adobe.com/flex/mx"
		 *						   contentCreationComplete="create()"
		 *						   >
		 *		<fx:Declarations>
		 *			<!-- Place non-visual elements (e.g., services, value objects) here -->
		 *		</fx:Declarations>
		 *		<fx:Script>
		 *			<![CDATA[
		 *				import com.automata.components.HangulAutomata;
		 *				import com.automata.data.HangulInfo;
		 *				import com.automata.data.WriteInfo;
		 *				import com.automata.status.HangulCode;
		 *				
		 *				import mx.core.IUITextField;
		 *				import mx.core.mx_internal;
		 *				
		 *				private var automata:HangulAutomata;
		 *				private var textField:IUITextField;
		 *				private var currIndex:int = -1;
		 *				
		 *				private function create():void {
		 *					textField = output.mx_internal::getTextField();
		 *					
		 *					automata = new HangulAutomata();
		 *					updateOutput( automata.addHan( HangulCode.CHOSEONG_KIYEOK ) );	// ㄱ
		 *					updateOutput( automata.addHan( HangulCode.JUNGSEONG_A ) );		// 가
		 *					updateOutput( automata.addHan( HangulCode.JONGSEONG_KIYEOK ) );	// [각]
		 *					updateOutput( automata.addHan( HangulCode.CHOSEONG_HIEUH ) );	// ㅎ
		 *					updateOutput( automata.addHan( HangulCode.JUNGSEONG_A ) );		// [하]
		 *				}
		 *				
		 *				private function updateOutput(info:WriteInfo):void {
		 *					var newHangul:String;
		 *					if (info.prevHangulInfo) {
		 *						textField.replaceText(currIndex, (currIndex + 1), "");
		 *						newHangul = info.prevHangulInfo.hangul;
		 *					}
		 *					if (info.currHangulInfo) {
		 *						newHangul = info.currHangulInfo.hangul;
		 *						currIndex++;
		 *					}
		 *					textField.appendText( newHangul );
		 *				}
		 *			]]>
		 * 		</fx:Script>
		 *		<s:Group width="100%" height="100%">
		 *			<mx:TextInput id="output" horizontalCenter="0" verticalCenter="0" />
		 *		</s:Group>
		 *	</s:WindowedApplication>
		 * </listing>
		 * 
		 */		
		public function addHan(hangulCode:uint):WriteInfo 
		{
			if (hangulCode == 0) {
				initializeCode();
				return null;
			}
			
			var writeInfo:WriteInfo = new WriteInfo();
			var prevInfo:HangulInfo = getHangulInfo( prevCode );
			var newJongCode:uint;
			if (prevCode) {
				switch (getHangulType(prevCode)) {
					case CHO: //초성만
						switch (getHangulType(hangulCode)) {
							case JUNG:
								prevCode = combineHangul(prevCode, hangulCode);
								writeInfo.prevHangulInfo = getHangulInfo( prevCode );
								break;
							default:
								prevCode = hangulCode;
								writeInfo.currHangulInfo = getHangulInfo( prevCode );
								break;
						}
					break;
					case JUNG: //중성만
						prevCode = hangulCode;
						writeInfo.currHangulInfo = getHangulInfo( prevCode );
					break;
					case JONG: //종성만
						prevCode = hangulCode;
						writeInfo.currHangulInfo = getHangulInfo( prevCode );
					break;
					case (CHO | JUNG):
						switch (getHangulType(hangulCode)) {
							case CHO:
								newJongCode = HangulUtil.convertCho2Jong(hangulCode);
								if (newJongCode == HangulCode.JONGSEONG_NONE) {
									prevCode = hangulCode;
									writeInfo.currHangulInfo = getHangulInfo( prevCode );
								} else {
									prevCode = combineHangul(prevInfo.choseongCode, prevInfo.jungseongCode, newJongCode);
									writeInfo.prevHangulInfo = getHangulInfo( prevCode );
								}
							break;
							case JUNG:
								//TODO:우 + ㅣ = 위 고려!
								var newJungCode:uint = HangulUtil.combineJungSeongs(prevInfo.jungseongCode, hangulCode);
								if (prevInfo.jungseongCode == newJungCode) {
									prevCode = hangulCode;
									writeInfo.currHangulInfo = getHangulInfo( prevCode );
								} else {
									prevCode = combineHangul(prevInfo.choseongCode, newJungCode);
									writeInfo.prevHangulInfo = getHangulInfo( prevCode );
								}
							break;
							case JONG:
								prevCode = combineHangul(prevInfo.choseongCode, prevInfo.jungseongCode, hangulCode);
								writeInfo.prevHangulInfo = getHangulInfo( prevCode );
							break;
						}
					break;
					case (CHO | JUNG | JONG):
						switch (getHangulType(hangulCode)) {
							case CHO:
								newJongCode = HangulUtil.combineJongSeongs(prevInfo.jongseongCode, HangulUtil.convertCho2Jong(hangulCode));
								if (prevInfo.jongseongCode == newJongCode) {
									prevCode = hangulCode;
									writeInfo.currHangulInfo = getHangulInfo( prevCode );
								} else {
									prevCode = combineHangul(prevInfo.choseongCode, prevInfo.jungseongCode, newJongCode);
									writeInfo.prevHangulInfo = getHangulInfo( prevCode );
								}
							break;
							case JUNG:
								var jc:Array = HangulUtil.convertJong2JC( prevInfo.jongseongCode );
								if (jc) {
									prevCode = combineHangul(jc[1], hangulCode);
									writeInfo.prevHangulInfo = getHangulInfo( combineHangul(prevInfo.choseongCode, prevInfo.jungseongCode, jc[0]) );
									writeInfo.currHangulInfo = getHangulInfo( prevCode );
								} else {
									prevCode = hangulCode;
									writeInfo.currHangulInfo = getHangulInfo( prevCode );
								}
							break;
							case JONG:
								newJongCode = HangulUtil.combineJongSeongs(prevInfo.jongseongCode, hangulCode);
								if (prevInfo.jongseongCode == newJongCode) {
									prevCode = hangulCode;
									writeInfo.prevHangulInfo = getHangulInfo( prevCode );
								} else {
									prevCode = combineHangul(prevInfo.choseongCode, prevInfo.jungseongCode, newJongCode);
									writeInfo.prevHangulInfo = getHangulInfo( prevCode );
								}
							break;
						}
					break;
				}
			} else {
				prevCode = hangulCode;
				writeInfo.currHangulInfo = getHangulInfo( prevCode );
			}
			
			return writeInfo;
		}
		
		private function getHangulInfo( hangulCode:uint ):HangulInfo {
			if (hangulCode == 0) {
				return null;
			}
			
			var tmp:int = hangulCode - 0xAC00;
			var jong:int = tmp % 28;
			tmp = Math.floor(tmp/28);
			var jung:int = tmp % 21;
			tmp = Math.floor(tmp/21);
			var cho:int = tmp;
			
			//필터링 - 일반 자모영역을 호환 자모영역으로 바꿔줌. 
			if ((hangulCode >= 0x1100) && (hangulCode <= 0x115E)) {
				// 자음만 있을 경우
				hangulCode = HangulUtil.convertCho2CompatiCho(hangulCode);
			} else if ((hangulCode >= 0x1161) && (hangulCode <= 0x11A7)) {
				// 모음만 있을 경우
				hangulCode = hangulCode + 0x1FEE;
			}
			
			var info:HangulInfo = new HangulInfo();
			info.hangul = String.fromCharCode( hangulCode );
			info.choseongCode = info.choseongCode + cho;
			info.jungseongCode = info.jungseongCode + jung;
			info.jongseongCode = info.jongseongCode +jong;
			
			return info;
		}
		
		private function combineHangul(choCode:uint = 0x1100, jungCode:uint = 0x1161, jongCode:uint = 0x11A7):uint {
			choCode = choCode - 0x1100;
			jungCode = jungCode - 0x1161;
			jongCode = jongCode - 0x11A7;
			return ((choCode * 21) + jungCode) * 28 + jongCode + 0xAC00;
		}
		
		private function getHangulType( hangulCode:uint ):int {
			var result:int = 0;
			if ((hangulCode >= 0x1100) && (hangulCode <= 0x115E)) {
				//초성만
				result = CHO;
			} else if ((hangulCode >= 0x1161) && (hangulCode <= 0x11A7)) {
				//중성만
				result = JUNG;
			} else if ((hangulCode >= 0x11A8) && (hangulCode <= 0x11FB)) {
				//종성만
				result = JONG;
			} else if ((hangulCode >= 0xAC00) && (hangulCode <= 0xD7AF)) {
				//조합
				var tmp:int = hangulCode - 0xAC00;
				var jong:int = tmp % 28;
				tmp = Math.floor(tmp/28);
				var jung:int = tmp % 21;
				tmp = Math.floor(tmp/21);
				var cho:int = tmp;
				result = CHO | JUNG;
				if (jong > 0) {
					result = result | JONG;
				}
			}
			
			return result;
		}
	}

}